
/**
 * @file a52enc.c
 * A/52 encoder
 */

#include "common.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "a52.h"
#include "bitalloc.h"
#include "bitalloc_core.h"
#include "crc.h"
#include "mdct.h"
#include "window.h"
#include "exponent.h"
#include "dynrng.h"
#include "convert.h"

static const uint8_t a52_rematrix_band_tab[5] = { 13, 25, 37, 61, 252 };

static void copy_samples(A52Context *ctx);
static void store_samples(A52Context *ctx);
static int convert_samples_from_src(A52Context *ctx, const void *vsrc, int count);

void alsophila_set_defaults(AlsophilaContext *s)
{
    assert(s != NULL);

    /**
     * These 4 must be set explicitly before initialization.
     * There are utility functions to help setting acmod and lfe.
     */
    s->channels = -1;
    s->samplerate = -1;
    s->acmod = -1;
    s->lfe = -1;

    s->verbose                      = 1;    // print dynamic 1-line summary
    s->sample_format                = A52_SAMPLE_FMT_S16;
    s->private_context              = NULL; // allocated during init
    s->params.encoding_mode         = ALSOPHILA_ENC_MODE_CBR;
    s->params.bitrate               = 0;    // chosen by libalsophila
    s->params.quality               = 240;  // snr offset = 0
    s->params.bwcode                = -1;   // chosen by libalsophila
    s->params.use_rematrixing       = 1;    // use rematrixing
    s->params.use_block_switching   = 0;    // do not use block switching
    s->params.use_bw_filter         = 0;    // do not use bandwidth filter
    s->params.use_dc_filter         = 0;    // do not use dc filter
    s->params.use_lfe_filter        = 0;    // do not use lfe filter
    s->params.bitalloc_fast         = 0;    // do not use fast bit allocation
    s->params.expstr_search         = 8;    // expstr search size = 8
    s->params.dynrng_profile        = DYNRNG_PROFILE_NONE;
    s->params.min_bwcode            = 0;    // minimum bandwidth, 28.5%
    s->params.max_bwcode            = 60;   // maximum bandwidth, 98.8%
    s->params.use_coupling          = 0;    // do not use channel coupling
    s->params.coupling_start        = -1;   // chosen by libalsophila
    s->params.coupling_end          = -1;   // chosen by libalsophila
    s->params.coupling_blocks       = 3;    // new coords every other block

    s->meta.cmixlev                 = 0;    // -3.0 dB center mix level
    s->meta.surmixlev               = 0;    // -3.0 dB surround mix level
    s->meta.dsurmod                 = 0;    // not dolby surround mode
    s->meta.dialnorm                = 31;   // -31 dB dialog level
    s->meta.xbsi1e                  = 0;    // do not encode extended bsi 1
    s->meta.dmixmod                 = 0;    // preferred downmix not indicated
    s->meta.ltrtcmixlev             = 4;    // -3.0 dB LtRt center mix level
    s->meta.ltrtsmixlev             = 4;    // -3.0 dB LtRt surround mix level
    s->meta.lorocmixlev             = 4;    // -3.0 dB LoRo center mix level
    s->meta.lorosmixlev             = 4;    // -3.0 dB LoRo surround mix level
    s->meta.xbsi2e                  = 0;    // do not encode extended bsi 2
    s->meta.dsurexmod               = 0;    // Dolby EX not indicated
    s->meta.dheadphonmod            = 0;    // Dolby Headphone not indicated
    s->meta.adconvtyp               = 0;    // standard A/D converter type

    s->initial_samples = NULL;
}

static void init_frame_header(A52Context *ctx)
{
    BitWriter bwc;
    BitWriter *bw = &bwc;
    int i;

    bitwriter_init(bw, ctx->header_data, 14);

    bitwriter_writebits(bw, 16, 0x0B77); /* frame header */
    bitwriter_writebits(bw, 16, 0); /* crc1: will be filled later */
    bitwriter_writebits(bw, 2, ctx->fscod);
    bitwriter_writebits(bw, 6, 0); /* frame size code will be filled in later */
    bitwriter_writebits(bw, 5, ctx->bsid);
    bitwriter_writebits(bw, 3, ctx->bsmod);
    bitwriter_writebits(bw, 3, ctx->acmod);
    switch (ctx->acmod) {
        case A52_ACMOD_DUAL_MONO:
        case A52_ACMOD_MONO:
            break;
        case A52_ACMOD_STEREO:
            bitwriter_writebits(bw, 2, ctx->meta.dsurmod);
            break;
        default:
            if (ctx->acmod & 0x01)
                bitwriter_writebits(bw, 2, ctx->meta.cmixlev);
            if (ctx->acmod & 0x04)
                bitwriter_writebits(bw, 2, ctx->meta.surmixlev);
            break;
    }
    bitwriter_writebits(bw, 1, ctx->lfe);

    for (i = (ctx->acmod == A52_ACMOD_DUAL_MONO); i >= 0; i--) {
        bitwriter_writebits(bw, 5, ctx->meta.dialnorm);
        bitwriter_writebits(bw, 1, 0); /* no compression control word */
        bitwriter_writebits(bw, 1, 0); /* no lang code */
        bitwriter_writebits(bw, 1, 0); /* no audio production info */
    }

    bitwriter_writebits(bw, 1, 0); /* no copyright */
    bitwriter_writebits(bw, 1, 1); /* original bitstream */
    if (ctx->bsid == 6) {
        // alternate bit stream syntax
        bitwriter_writebits(bw, 1, ctx->meta.xbsi1e);
        if (ctx->meta.xbsi1e) {
            bitwriter_writebits(bw, 2, ctx->meta.dmixmod);
            bitwriter_writebits(bw, 3, ctx->meta.ltrtcmixlev);
            bitwriter_writebits(bw, 3, ctx->meta.ltrtsmixlev);
            bitwriter_writebits(bw, 3, ctx->meta.lorocmixlev);
            bitwriter_writebits(bw, 3, ctx->meta.lorosmixlev);
        }
        bitwriter_writebits(bw, 1, ctx->meta.xbsi2e);
        if (ctx->meta.xbsi2e) {
            bitwriter_writebits(bw, 2, ctx->meta.dsurexmod);
            bitwriter_writebits(bw, 2, ctx->meta.dheadphonmod);
            bitwriter_writebits(bw, 1, ctx->meta.adconvtyp);
            bitwriter_writebits(bw, 9, 0);
        }
    } else {
        bitwriter_writebits(bw, 1, 0); // timecod1e
        bitwriter_writebits(bw, 1, 0); // timecod2e
    }
    bitwriter_writebits(bw, 1, 0); /* no addtional bit stream info */
    ctx->header_size = bitwriter_bitcount(bw);
    bitwriter_flushbits(bw);
}

int alsophila_encode_init(AlsophilaContext *s)
{
    A52Context *ctx;
    int i, ch, blk;
    int brate, last_quality;

    assert(s != NULL);

    ctx = calloc(sizeof(A52Context), 1);
    if (!ctx) {
        fprintf(stderr, "error allocating memory for A52Context\n");
        return -1;
    }
    s->private_context = ctx;

    a52_set_converter(ctx, s->sample_format);

    // channel configuration
    if (s->channels < 1 || s->channels > 6) {
        fprintf(stderr, "invalid number of channels\n");
        return -1;
    }
    if (s->acmod > 7) {
        fprintf(stderr, "invalid acmod\n");
        return -1;
    }
    if (s->channels == 6 && !s->lfe) {
        fprintf(stderr, "6-channel audio must have LFE channel\n");
        return -1;
    }
    if (s->channels == 1 && s->lfe) {
        fprintf(stderr, "cannot encode stand-alone LFE channel\n");
        return -1;
    }
    ctx->acmod = s->acmod;
    ctx->lfe = s->lfe;
    ctx->n_all_channels = s->channels;
    ctx->n_channels = s->channels - s->lfe;
    ctx->lfe_channel = ctx->n_channels + 1;

    ctx->params = s->params;
    ctx->meta = s->meta;

    // frequency
    switch (s->samplerate) {
        case 48000:
        case 24000:
        case 12000: ctx->fscod = 0; break;
        case 44100:
        case 22050:
        case 11025: ctx->fscod = 1; break;
        case 32000:
        case 16000:
        case  8000: ctx->fscod = 2; break;
        default:
            fprintf(stderr, "invalid sample rate\n");
            return -1;
    }
    ctx->halfratecod = (s->samplerate < 32000) + (s->samplerate < 16000);
    ctx->sample_rate = s->samplerate;

    if (ctx->halfratecod) {
        // DolbyNet
        ctx->bsid = 8 + ctx->halfratecod;
    } else if (ctx->meta.xbsi1e || ctx->meta.xbsi2e) {
        // alternate bit stream syntax
        ctx->bsid = 6;
    } else {
        // normal AC-3
        ctx->bsid = 8;
    }
    ctx->bsmod = 0;
    ctx->last_samples_count = -1;

    // bitrate & frame size
    brate = ctx->params.bitrate;
    if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_CBR) {
        if (brate == 0) {
            switch (ctx->n_channels) {
                case 1: brate =  96; break;
                case 2: brate = 192; break;
                case 3: brate = 256; break;
                case 4: brate = 384; break;
                case 5: brate = 448; break;
            }
        }
    } else if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_VBR) {
        if (ctx->params.quality < 0 || ctx->params.quality > 1023) {
            fprintf(stderr, "invalid quality setting\n");
            return -1;
        }
        if (brate == 0)
            brate = 640;
    } else {
        return -1;
    }

    for (i = 0; i < 19; i++) {
        if ((a52_bitrate_tab[i] >> ctx->halfratecod) == brate)
            break;
    }
    if (i == 19) {
        if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_CBR) {
            fprintf(stderr, "invalid bitrate\n");
            return -1;
        }
        i = 18;
    }
    ctx->frmsizecod = i*2;
    ctx->target_bitrate = a52_bitrate_tab[i] >> ctx->halfratecod;

    if (ctx->params.expstr_search < 1 || ctx->params.expstr_search > 32) {
        fprintf(stderr, "invalid exponent strategy search size: %d\n",
                ctx->params.expstr_search);
        return -1;
    }

    a52_crc16_init();
    a52_window_init();
    a52_exponent_init();
    a52_mdct_init(ctx);

    // can't do block switching with low sample rate due to the high-pass filter
    if (ctx->sample_rate <= 16000) {
        ctx->params.use_block_switching = 0;
    }

    last_quality = 240;
    if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_VBR) {
        last_quality = ctx->params.quality;
    } else if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_CBR) {
        last_quality = ((((ctx->target_bitrate/ctx->n_channels)*35)/24)+95)+(25*ctx->halfratecod);
    }

    if (ctx->params.coupling_start < -1 || ctx->params.coupling_start > 18) {
        fprintf(stderr, "invalid coupling start subband\n");
        return -1;
    }
    if (ctx->params.coupling_end != -1 && (ctx->params.coupling_end < 3 ||
            ctx->params.coupling_end > 18)) {
        fprintf(stderr, "invalid coupling end subband\n");
        return -1;
    }
    if (ctx->params.coupling_start < 0) {
        ctx->params.coupling_start = (ctx->acmod == A52_ACMOD_STEREO) ? 6 : 10;
    }
    if (ctx->params.coupling_end < 0) {
        ctx->params.coupling_end = 15;
    }
    if (ctx->params.coupling_end <= ctx->params.coupling_start) {
        fprintf(stderr, "invalid coupling range\n");
        return -1;
    }

    if (ctx->params.coupling_blocks < 1 || (ctx->params.coupling_blocks > 3 &&
            ctx->params.coupling_blocks != 6)) {
        fprintf(stderr, "invalid coupling_blocks value. must be 1, 2, 3, or 6\n");
        return -1;
    }

    if (ctx->params.bwcode < -2 || ctx->params.bwcode > 60) {
        fprintf(stderr, "invalid bandwidth code\n");
        return -1;
    }
    if (ctx->params.bwcode < 0) {
        int cutoff = ((last_quality-120) * 120) + 4000;
        ctx->fixed_bwcode = ((cutoff * 512 / ctx->sample_rate) - 73) / 3;
        if (ctx->params.bwcode == -2) {
            if (ctx->params.use_coupling) {
                fprintf(stderr, "cannot use coupling & variable bandwidth (yet)\n");
                return -1;
            }
            if (ctx->params.min_bwcode < 0 || ctx->params.min_bwcode > 60 ||
               ctx->params.max_bwcode < 0 || ctx->params.max_bwcode > 60 ||
               ctx->params.min_bwcode > ctx->params.max_bwcode) {
                fprintf(stderr, "invalid min/max bandwidth code\n");
                return -1;
            }
            if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_VBR) {
                fprintf(stderr, "variable bandwidth mode cannot be used with variable bitrate mode\n");
                return -1;
            }
            ctx->fixed_bwcode = 60;
        }
        ctx->fixed_bwcode = CLIP(ctx->fixed_bwcode, ctx->params.min_bwcode,
                                 ctx->params.max_bwcode);
    } else {
        ctx->fixed_bwcode = ctx->params.bwcode;
    }

    if (ctx->params.use_coupling && ctx->params.use_block_switching) {
        // TODO: turn off coupling for short blocks
        // for now, turn off block switching if coupling is used
        ctx->params.use_block_switching = 0;
    }

    // initialize transient-detect filters (one for each channel)
    // cascaded biquad direct form I high-pass w/ cutoff of 8 kHz
    if (ctx->params.use_block_switching) {
        for (i = 0; i < ctx->n_all_channels; i++) {
            ctx->bs_filter[i].type = FILTER_TYPE_HIGHPASS;
            ctx->bs_filter[i].cascaded = 1;
            ctx->bs_filter[i].cutoff = 8000;
            ctx->bs_filter[i].samplerate = (double)ctx->sample_rate;
            if (a52_filter_init(&ctx->bs_filter[i], FILTER_ID_BIQUAD_I)) {
                fprintf(stderr, "error initializing transient-detect filter\n");
                return -1;
            }
        }
    }

    // initialize DC filters (one for each channel)
    // one-pole high-pass w/ cutoff of 3 Hz
    if (ctx->params.use_dc_filter) {
        for (i = 0; i < ctx->n_all_channels; i++) {
            ctx->dc_filter[i].type = FILTER_TYPE_HIGHPASS;
            ctx->dc_filter[i].cascaded = 0;
            ctx->dc_filter[i].cutoff = 3;
            ctx->dc_filter[i].samplerate = (double)ctx->sample_rate;
            if (a52_filter_init(&ctx->dc_filter[i], FILTER_ID_ONEPOLE)) {
                fprintf(stderr, "error initializing dc filter\n");
                return -1;
            }
        }
    }

    // initialize bandwidth filters (one for each channel)
    // butterworth 2nd order cascaded direct form II low-pass
    if (ctx->params.use_bw_filter) {
        int cutoff;
        if (ctx->params.bwcode == -2) {
            fprintf(stderr, "cannot use bandwidth filter with variable bandwidth\n");
            return -1;
        }
        cutoff = (((ctx->fixed_bwcode * 3) + 73) * ctx->sample_rate) / 512;
        if (cutoff < 4000) {
            // disable bandwidth filter if cutoff is below 4000 Hz
            ctx->params.use_bw_filter = 0;
        } else {
            for (i = 0; i < ctx->n_channels; i++) {
                ctx->bw_filter[i].type = FILTER_TYPE_LOWPASS;
                ctx->bw_filter[i].cascaded = 1;
                ctx->bw_filter[i].cutoff = (double)cutoff;
                ctx->bw_filter[i].samplerate = (double)ctx->sample_rate;
                if (a52_filter_init(&ctx->bw_filter[i], FILTER_ID_BUTTERWORTH_II)) {
                    fprintf(stderr, "error initializing bandwidth filter\n");
                    return -1;
                }
            }
        }
    }

    // initialize LFE filter
    // butterworth 2nd order cascaded direct form II low-pass w/ cutoff of 120 Hz
    if (ctx->params.use_lfe_filter) {
        if (!ctx->lfe) {
            fprintf(stderr, "cannot use lfe filter. no lfe channel\n");
            return -1;
        }
        ctx->lfe_filter.type = FILTER_TYPE_LOWPASS;
        ctx->lfe_filter.cascaded = 1;
        ctx->lfe_filter.cutoff = 120;
        ctx->lfe_filter.samplerate = (double)ctx->sample_rate;
        if (a52_filter_init(&ctx->lfe_filter, FILTER_ID_BUTTERWORTH_II)) {
            fprintf(stderr, "error initializing lfe filter\n");
            return -1;
        }
    }

    ctx->bit_cnt = 0;
    ctx->sample_cnt = 0;
    ctx->last_quality = last_quality;

    // alloate input audio buffer and setup pointers
    ctx->frame.input_audio = calloc(1, (A52_SAMPLES_PER_FRAME + 256) *
                                    ctx->n_all_channels * sizeof(double));
    for (ch = 0; ch < ctx->n_all_channels; ch++) {
        for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
            ctx->frame.blocks[blk].input_samples[ch] = &ctx->frame.input_audio[256*(7*ch+blk)];
        }
        ctx->frame.input_samples[ch] = ctx->frame.blocks[1].input_samples[ch];
    }

    // copy initial samples
    if (s->initial_samples) {
        double *samples = malloc(A52_SAMPLES_PER_FRAME * ctx->n_all_channels * sizeof(double));
        memset(samples, 0, (A52_SAMPLES_PER_FRAME - 256) * ctx->n_all_channels * sizeof(double));
        memcpy(samples + (A52_SAMPLES_PER_FRAME - 256) * ctx->n_all_channels, s->initial_samples, 256 * ctx->n_all_channels * sizeof(double));
        convert_samples_from_src(ctx, samples, A52_SAMPLES_PER_FRAME);
        free(samples);
        // copy samples with filters applied
        copy_samples(ctx);
        store_samples(ctx);
    }

    init_frame_header(ctx);

    return 0;
}

static int frame_init(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    A52Block *block;
    int blk, ch, bnd, bnd1;

    // fixed coupling parameters
    frame->cpl_in_use = ctx->params.use_coupling;
    if (frame->cpl_in_use) {
        frame->cpl_start_subband = ctx->params.coupling_start;
        frame->cpl_end_subband = ctx->params.coupling_end;

        frame->num_cpl_subbands = frame->cpl_end_subband - frame->cpl_start_subband;
        frame->num_cpl_bands = a52_num_cpl_bands_tab[frame->cpl_start_subband][frame->cpl_end_subband];
        bnd1 = 0;
        frame->cpl_band_size[bnd1] = 12;
        for (bnd = frame->cpl_start_subband+1; bnd < frame->cpl_end_subband; bnd++) {
            if (a52_default_coupling_band_struct_tab[bnd]) {
                frame->cpl_band_size[bnd1] += 12;
            } else {
                bnd1++;
                frame->cpl_band_size[bnd1] = 12;
            }
        }
        frame->start_freq[CPL_CH] = frame->cpl_start_subband * 12 + 37;
        frame->end_freq[CPL_CH]   = frame->cpl_end_subband   * 12 + 37;
        frame->cpl_coord_interval = 6 / ctx->params.coupling_blocks;
    }
    // if coupling is used, couple all channels by default
    for (ch = 1; ch <= ctx->n_channels; ch++) {
        frame->channel_in_cpl[ch] = frame->cpl_in_use;
    }
    frame->channel_in_cpl[ctx->lfe_channel] = 0;

    // set number of rematrixing bands
    if (ctx->acmod == A52_ACMOD_STEREO) {
        frame->num_rematrixing_bands = 4;
        if (frame->cpl_in_use) {
            frame->num_rematrixing_bands -= (frame->cpl_start_subband <= 2);
            frame->num_rematrixing_bands -= (frame->cpl_start_subband == 0);
        }
    }

    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block = &frame->blocks[blk];
        block->block_num = blk;
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            block->blksw[ch] = 0;
            block->dithflag[ch] = 1;
        }

        if (frame->cpl_in_use)
            block->new_cpl_coords = !(blk % frame->cpl_coord_interval);
    }

    if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_CBR) {
        frame->bit_rate = ctx->target_bitrate;
        frame->frmsizecod = ctx->frmsizecod;
        frame->frame_size_min = frame->bit_rate * 96000 / ctx->sample_rate;
        frame->frame_size = frame->frame_size_min;
    }

    if (frame->cpl_in_use) {
        frame->bwcode = (frame->end_freq[CPL_CH] - 73) / 3;
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            if (frame->channel_in_cpl[ch])
                frame->ncoefs[ch] = frame->start_freq[CPL_CH];
            else
                frame->ncoefs[ch] = (frame->bwcode * 3) + 73;
            frame->start_freq[ch] = 0;
            frame->end_freq[ch] = frame->ncoefs[ch];
        }
        frame->ncoefs[CPL_CH] = frame->end_freq[CPL_CH] - frame->start_freq[CPL_CH];
    } else {
        if (ctx->params.bwcode == -2) {
            frame->bwcode = 60;
        } else {
            frame->bwcode = ctx->fixed_bwcode;
        }
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            frame->ncoefs[ch] = (frame->bwcode * 3) + 73;
            frame->start_freq[ch] = 0;
            frame->end_freq[ch] = frame->ncoefs[ch];
        }
    }
    frame->ncoefs[ctx->lfe_channel] = 7;
    frame->start_freq[ctx->lfe_channel] = 0;
    frame->end_freq[ctx->lfe_channel] = frame->ncoefs[ctx->lfe_channel];

    frame->frame_bits = 0;
    frame->exp_bits = 0;
    frame->mant_bits = 0;

    // default bit allocation params
    frame->sdecaycod = 2;
    frame->fdecaycod = 1;
    frame->sgaincod = 1;
    frame->dbkneecod = 2;
    frame->floorcod = 7;

    return 0;
}

/* output the A52 frame header */
static void output_frame_header(A52Context *ctx)
{
    A52Frame *f = &ctx->frame;
    BitWriter *bw = &ctx->bw;
    int i, frmsizecod = f->frmsizecod+(f->frame_size-f->frame_size_min);
    int hdr_bytes = ctx->header_size >> 3;
    int last_bits = ctx->header_size & 0x7;

    ctx->header_data[4] = (ctx->header_data[4] & 0xC0) | (frmsizecod & 0x3F);

    for (i = 0; i < hdr_bytes; i++)
        bitwriter_writebits(bw, 8, ctx->header_data[i]);

    bitwriter_writebits(bw, last_bits, ctx->header_data[i] >> (8-last_bits));
}

/* symmetric quantization on 'levels' levels */
#define sym_quant(c, e, levels) \
    ((((((levels) * (c)) >> (24-(e))) + 1) >> 1) + ((levels) >> 1))

/* asymmetric quantization on 2^qbits levels */
static inline int asym_quant(int c, int e, int qbits)
{
    int lshift, m, v;

    lshift = e + (qbits-1) - 24;
    if (lshift >= 0)
        v = c << lshift;
    else
        v = c >> (-lshift);

    m = (1 << (qbits-1));
    v = CLIP(v, -m, m-1);

    return v & ((1 << qbits)-1);
}

static void quant_mant_ch(double *mdct_coef, uint8_t *exp, uint8_t *bap,
                          uint16_t *qmant, int start, int end,
                          uint16_t *qmant_ptr[3], int mant_cnt[3])
{
    int i, c, e, b, v;

    for (i = start; i < end; i++) {
        c = (int)(mdct_coef[i] * (1 << 24));
        e = exp[i];
        b = bap[i];
        switch (b) {
            case 0:
                v = 0;
                break;
            case 1:
                v = sym_quant(c, e, 3);
                if (mant_cnt[0] == 0) {
                    qmant_ptr[0] = &qmant[i];
                    v = 9 * v;
                } else if (mant_cnt[0] == 1) {
                    *qmant_ptr[0] += 3 * v;
                    v = 128;
                } else {
                    *qmant_ptr[0] += v;
                    v = 128;
                }
                mant_cnt[0] = (mant_cnt[0] + 1) % 3;
                break;
            case 2:
                v = sym_quant(c, e, 5);
                if (mant_cnt[1] == 0) {
                    qmant_ptr[1] = &qmant[i];
                    v = 25 * v;
                } else if (mant_cnt[1] == 1) {
                    *qmant_ptr[1] += 5 * v;
                    v = 128;
                } else {
                    *qmant_ptr[1] += v;
                    v = 128;
                }
                mant_cnt[1] = (mant_cnt[1] + 1) % 3;
                break;
            case 3:
                v = sym_quant(c, e, 7);
                break;
            case 4:
                v = sym_quant(c, e, 11);
                if (mant_cnt[2]== 0) {
                    qmant_ptr[2] = &qmant[i];
                    v = 11 * v;
                } else {
                    *qmant_ptr[2] += v;
                    v = 128;
                }
                mant_cnt[2] = (mant_cnt[2] + 1) % 2;
                break;
            case 5:
                v = sym_quant(c, e, 15);
                break;
            case 14:
                v = asym_quant(c, e, 14);
                break;
            case 15:
                v = asym_quant(c, e, 16);
                break;
            default:
                v = asym_quant(c, e, b - 1);
        }
        qmant[i] = v;
    }
}

static void quantize_mantissas(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    A52Block *block;
    uint16_t *qmant_ptr[3];
    int blk, ch;
    int mant_cnt[3];
    int got_cpl;

    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block = &frame->blocks[blk];
        mant_cnt[0] = mant_cnt[1] = mant_cnt[2] = 0;
        qmant_ptr[0] = qmant_ptr[1] = qmant_ptr[2] = NULL;
        got_cpl = !frame->cpl_in_use;
        for (ch = 1; ch <= ctx->n_all_channels; ch++) {
            quant_mant_ch(block->mdct_coef[ch], block->exp[ch], block->bap[ch],
                          block->qmant[ch], frame->start_freq[ch],
                          frame->end_freq[ch], qmant_ptr, mant_cnt);
            if (!got_cpl && frame->channel_in_cpl[ch]) {
                quant_mant_ch(block->mdct_coef[CPL_CH], block->exp[CPL_CH],
                              block->bap[CPL_CH], block->qmant[CPL_CH],
                              frame->start_freq[CPL_CH], frame->end_freq[CPL_CH],
                              qmant_ptr, mant_cnt);
                got_cpl = 1;
            }
        }
    }
}

/* Output each audio block. Return number of bits written. */
int a52_output_audio_blocks(A52Context *ctx, int write)
{
    BitWriter bwc;
    A52Frame *frame = &ctx->frame;
    A52Block *block;
    BitWriter *bw = &bwc;
    int blk, ch, i, baie, bnd;
    int got_cpl, ch_tmp=0;
    int start_bits = 0;

    if (write) {
        bw = &ctx->bw;
        start_bits = bitwriter_bitcount(bw);
    } else {
        bitwriter_init(bw, NULL, A52_MAX_CODED_FRAME_SIZE*2);
    }

    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block = &frame->blocks[blk];
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            bitwriter_writebits(bw, 1, block->blksw[ch]);
        }
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            bitwriter_writebits(bw, 1, block->dithflag[ch]);
        }
        if (ctx->params.dynrng_profile == DYNRNG_PROFILE_NONE) {
            bitwriter_writebits(bw, 1, 0); // no dynamic range
            if (ctx->acmod == A52_ACMOD_DUAL_MONO) {
                bitwriter_writebits(bw, 1, 0); // no dynamic range 2
            }
        } else {
            bitwriter_writebits(bw, 1, 1);
            bitwriter_writebits(bw, 8, block->dynrng);
            if (ctx->acmod == A52_ACMOD_DUAL_MONO) {
                bitwriter_writebits(bw, 1, 1);
                bitwriter_writebits(bw, 8, block->dynrng);
            }
        }

        // coupling
        bitwriter_writebits(bw, 1, !blk); // new coupling strategy
        if (!blk) {
            // must define coupling strategy in block 0
            bitwriter_writebits(bw, 1, frame->cpl_in_use);
            if (frame->cpl_in_use) {
                for (ch = 1; ch <= ctx->n_channels; ch++) {
                    bitwriter_writebits(bw, 1, frame->channel_in_cpl[ch]);
                }
                if (ctx->acmod == A52_ACMOD_STEREO) {
                    bitwriter_writebits(bw, 1, 0); // no phase flags
                }
                bitwriter_writebits(bw, 4, frame->cpl_start_subband);
                bitwriter_writebits(bw, 4, frame->cpl_end_subband - 3);
                for (bnd = frame->cpl_start_subband+1; bnd < frame->cpl_end_subband; bnd++) {
                    bitwriter_writebits(bw, 1, a52_default_coupling_band_struct_tab[bnd]);
                }
            }
        }
        if (frame->cpl_in_use) {
            for (ch = 1; ch <= ctx->n_channels; ch++) {
                if (frame->channel_in_cpl[ch]) {
                    bitwriter_writebits(bw, 1, block->new_cpl_coords);
                    if (block->new_cpl_coords) {
                        bitwriter_writebits(bw, 2, block->master_cpl_coord[ch]);
                        for (bnd = 0; bnd < frame->num_cpl_bands; bnd++) {
                            bitwriter_writebits(bw, 4, block->cpl_coord_exp[ch][bnd]);
                            bitwriter_writebits(bw, 4, block->cpl_coord_mant[ch][bnd]);
                        }
                    }
                }
            }
            /* TODO: phase flags */
        }

        if (ctx->acmod == A52_ACMOD_STEREO) {
            int rematstr = !blk;
            bitwriter_writebits(bw, 1, rematstr);
            if (rematstr) {
                for (bnd = 0; bnd < frame->num_rematrixing_bands; bnd++) {
                    bitwriter_writebits(bw, 1, frame->rematflg[bnd]);
                }
            }
        }

        // exponent strategy
        for (ch = !frame->cpl_in_use; ch <= ctx->n_channels; ch++) {
            bitwriter_writebits(bw, 2, block->exp_strategy[ch]);
        }
        if (ctx->lfe) {
            bitwriter_writebits(bw, 1, block->exp_strategy[ctx->lfe_channel]);
        }

        for (ch = 1; ch <= ctx->n_channels; ch++) {
            if (block->exp_strategy[ch] != EXP_REUSE && !frame->channel_in_cpl[ch])
                bitwriter_writebits(bw, 6, frame->bwcode);
        }

        // exponents
        for (ch = !frame->cpl_in_use; ch <= ctx->n_all_channels; ch++) {
            if (block->exp_strategy[ch] != EXP_REUSE) {
                // first exponent
                bitwriter_writebits(bw, 4, block->grp_exp[ch][0] >> !ch);

                // delta-encoded exponent groups
                for (i = 1; i <= block->nexpgrps[ch]; i++) {
                    bitwriter_writebits(bw, 7, block->grp_exp[ch][i]);
                }

                // gain range info
                if (ch != ctx->lfe_channel && ch != CPL_CH) {
                    bitwriter_writebits(bw, 2, 0);
                }
            }
        }

        // bit allocation info
        baie = (block->block_num == 0);
        bitwriter_writebits(bw, 1, baie);
        if (baie) {
            bitwriter_writebits(bw, 2, frame->sdecaycod);
            bitwriter_writebits(bw, 2, frame->fdecaycod);
            bitwriter_writebits(bw, 2, frame->sgaincod);
            bitwriter_writebits(bw, 2, frame->dbkneecod);
            bitwriter_writebits(bw, 3, frame->floorcod);
        }

        // snr offset
        bitwriter_writebits(bw, 1, block->write_snr);
        if (block->write_snr) {
            bitwriter_writebits(bw, 6, frame->csnroffst);
            for (ch = !frame->cpl_in_use; ch <= ctx->n_all_channels; ch++) {
                bitwriter_writebits(bw, 4, frame->fsnroffst);
                bitwriter_writebits(bw, 3, block->fgaincod[ch]);
            }
        }

        // coupling leak
        if (frame->cpl_in_use) {
            bitwriter_writebits(bw, 1, baie);
            if (baie) {
                bitwriter_writebits(bw, 3, 0); // cplfleak
                bitwriter_writebits(bw, 3, 0); // cplsleak
            }
        }

        bitwriter_writebits(bw, 1, 0); // no delta bit allocation
        bitwriter_writebits(bw, 1, 0); // no data to skip

        // if not writing to output, skip mantissas
        if (!write)
            continue;

        // mantissas
        got_cpl = !frame->cpl_in_use;
        for (ch = 1; ch <= ctx->n_all_channels; ch++) {
            int b=0, q=0;
            if (!got_cpl && ch > 1 && frame->channel_in_cpl[ch-1]) {
                ch_tmp = ch-1;
                ch = CPL_CH;
                got_cpl = 1;
            }
            for (i = frame->start_freq[ch]; i < frame->end_freq[ch]; i++) {
                q = block->qmant[ch][i];
                b = block->bap[ch][i];
                switch (b) {
                    case 0:  break;
                    case 1:  if(q != 128) bitwriter_writebits(bw, 5, q);
                             break;
                    case 2:  if(q != 128) bitwriter_writebits(bw, 7, q);
                             break;
                    case 3:  bitwriter_writebits(bw, 3, q);
                             break;
                    case 4:  if(q != 128) bitwriter_writebits(bw, 7, q);
                             break;
                    case 14: bitwriter_writebits(bw, 14, q);
                             break;
                    case 15: bitwriter_writebits(bw, 16, q);
                             break;
                    default: bitwriter_writebits(bw, b - 1, q);
                }
            }
            if (ch == CPL_CH)
                ch = ch_tmp;
        }
    }

    return bitwriter_bitcount(bw) - start_bits;
}

static int output_frame_end(A52Context *ctx)
{
    uint8_t *frame;
    int fs, fs58, n, crc1, crc2, bitcount;

    fs = ctx->frame.frame_size;
    // align to 8 bits
    bitwriter_flushbits(&ctx->bw);
    // add zero bytes to reach the frame size
    frame = ctx->bw.buffer;
    bitcount = bitwriter_bitcount(&ctx->bw);
    n = (fs << 1) - 2 - (bitcount >> 3);
    if (n < 0) {
        fprintf(stderr, "data size exceeds frame size (frame=%d data=%d)\n",
                (fs << 1) - 2, bitcount >> 3);
        return -1;
    }
    if (n > 0)
        memset(&ctx->bw.buffer[bitcount>>3], 0, n);

    // compute crc1 for 1st 5/8 of frame
    fs58 = (fs >> 1) + (fs >> 3);
    crc1 = a52_crc16_calc(&frame[4], (fs58<<1)-4);
    crc1 = a52_crc16_zero(crc1, (fs58<<1)-2);
    frame[2] = crc1 >> 8;
    frame[3] = crc1;
    // double-check
    crc1 = a52_crc16_calc(&frame[2], (fs58<<1)-2);
    if (crc1 != 0)
        fprintf(stderr, "CRC ERROR\n");

    // compute crc2 for final 3/8 of frame
    crc2 = a52_crc16_calc(&frame[fs58<<1], ((fs - fs58) << 1) - 2);
    frame[(fs<<1)-2] = crc2 >> 8;
    frame[(fs<<1)-1] = crc2;

    return 0;
}

static void copy_samples(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    double buffer[A52_SAMPLES_PER_FRAME];
    double *in_audio;
    double *out_audio;
    double *temp;
    int ch, blk;
#define SWAP_BUFFERS temp=in_audio;in_audio=out_audio;out_audio=temp;

    for (ch = 0; ch < ctx->n_all_channels; ch++) {
        out_audio = buffer;
        in_audio = frame->input_samples[ch];
        // DC-removal high-pass filter
        if (ctx->params.use_dc_filter) {
            a52_filter_run(&ctx->dc_filter[ch], out_audio, in_audio,
                           A52_SAMPLES_PER_FRAME);
            SWAP_BUFFERS
        }
        if (ch < ctx->n_channels) {
            // channel bandwidth filter
            if (ctx->params.use_bw_filter) {
                a52_filter_run(&ctx->bw_filter[ch], out_audio, in_audio,
                               A52_SAMPLES_PER_FRAME);
                SWAP_BUFFERS
            }
            // block-switching high-pass filter
            if (ctx->params.use_block_switching) {
                a52_filter_run(&ctx->bs_filter[ch], out_audio, in_audio,
                               A52_SAMPLES_PER_FRAME);
                memcpy(frame->blocks[0].transient_samples[ch],
                       ctx->last_transient_samples[ch], 256 * sizeof(double));
                memcpy(&frame->blocks[0].transient_samples[ch][256], out_audio,
                       256 * sizeof(double));
                for (blk = 1; blk < A52_NUM_BLOCKS; blk++) {
                    memcpy(frame->blocks[blk].transient_samples[ch],
                           &out_audio[256*(blk-1)], 512 * sizeof(double));
                }
                memcpy(ctx->last_transient_samples[ch],
                       &out_audio[256*5], 256 * sizeof(double));
            }
        } else {
            // LFE bandwidth low-pass filter
            if (ctx->params.use_lfe_filter) {
                assert(ch == ctx->lfe_channel);
                a52_filter_run(&ctx->lfe_filter, out_audio, in_audio,
                               A52_SAMPLES_PER_FRAME);
                SWAP_BUFFERS
            }
        }

        /* if current audio is in local buffer, copy to context buffer */
        if (in_audio != frame->input_samples[ch]) {
            memcpy(frame->input_samples[ch], in_audio, A52_SAMPLES_PER_FRAME * sizeof(double));
        }
    }
#undef SWAP_BUFFERS
}

static void store_samples(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    int ch;

    for (ch = 0; ch < ctx->n_all_channels; ch++) {
        memcpy(frame->blocks[0].input_samples[ch],
               frame->blocks[5].input_samples[ch]+256,
               256 * sizeof(double));
    }
}

/* determines block length by detecting transients */
static int detect_transient(double *in)
{
    double *xx = in;
    int i, j;
    double level1[2];
    double level2[4];
    double level3[8];
    double tmax = 100.0 / 32768.0;
    double t1 = 0.100;
    double t2 = 0.075;
    double t3 = 0.050;

    // level 1 (2 x 256)
    for (i = 0; i < 2; i++) {
        level1[i] = 0;
        for (j = 0; j < 256; j++)
            level1[i] = MAX(fabs(xx[i*256+j]), level1[i]);
        if (level1[i] < tmax)
            return 0;
        if ((i > 0) && (level1[i] * t1 > level1[i-1]))
            return 1;
    }

    // level 2 (4 x 128)
    for (i = 1; i < 4; i++) {
        level2[i] = 0;
        for (j = 0; j < 128; j++)
            level2[i] = MAX(fabs(xx[i*128+j]), level2[i]);
        if ((i > 1) && (level2[i] * t2 > level2[i-1]))
            return 1;
    }

    // level 3 (8 x 64)
    for (i = 3; i < 8; i++) {
        level3[i] = 0;
        for (j = 0; j < 64; j++)
            level3[i] = MAX(fabs(xx[i*64+j]), level3[i]);
        if ((i > 3) && (level3[i] * t3 > level3[i-1]))
            return 1;
    }

    return 0;
}

static void generate_coefs(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    A52Block *block;
    int blk, ch, i;
    double windowed_samples[512];

    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block = &ctx->frame.blocks[blk];
        if (frame->cpl_in_use) {
            memset(block->mdct_coef[CPL_CH], 0, 256 * sizeof(double));
        }
        for (ch = 1; ch <= ctx->n_all_channels; ch++) {
            if (ctx->params.use_block_switching)
                block->blksw[ch] = detect_transient(block->transient_samples[ch-1]);
            else
                block->blksw[ch] = 0;
            a52_window_apply(windowed_samples, block->input_samples[ch-1]);
            if (block->blksw[ch])
                a52_mdct_256(ctx, block->mdct_coef[ch], windowed_samples);
            else
                a52_mdct_512(ctx, block->mdct_coef[ch], windowed_samples);
            if (!frame->cpl_in_use) {
                for (i = ctx->frame.end_freq[ch]; i < 256; i++)
                    block->mdct_coef[ch][i] = 0.0;
            } else {
                for (i = frame->start_freq[CPL_CH]; i < 256; i++)
                    block->mdct_coef[CPL_CH][i] += block->mdct_coef[ch][i];
            }
        }
    }
    /* calculate coupling coordinates for each channel */
    if (frame->cpl_in_use) {
        double amp[A52_MAX_CHANNELS];
        double coef;
        int bnd, j;
        block = &frame->blocks[0];

        j = frame->start_freq[CPL_CH];
        for (bnd = 0; bnd < frame->num_cpl_bands; bnd++) {
            for (blk = 0; blk < A52_NUM_BLOCKS; blk += frame->cpl_coord_interval) {
                int blk1;
                int exp[A52_MAX_CHANNELS];
                int coord[A52_MAX_CHANNELS];
                int j2 = j;

                /* calculate amplitudes for coupling channel and for all
                   coupled channels */
                memset(amp, 0, sizeof(amp));
                for (i = 0; i < frame->cpl_band_size[bnd]; i++, j2++) {
                    for (ch = CPL_CH; ch <= ctx->n_channels; ch++) {
                        if (ch == CPL_CH || frame->channel_in_cpl[ch]) {
                            for (blk1 = 0; blk1 < frame->cpl_coord_interval; blk1++) {
                                coef = frame->blocks[blk+blk1].mdct_coef[ch][j2];
                                amp[ch] += (coef * coef);
                            }
                        }
                    }
                }
                for (ch = CPL_CH; ch <= ctx->n_channels; ch++) {
                    if (ch == CPL_CH || frame->channel_in_cpl[ch])
                        amp[ch] /= (i * frame->cpl_coord_interval);
                }

                /* calculate coupling coordinate exponents */
                for (ch = 1; ch <= ctx->n_channels; ch++) {
                    if (frame->channel_in_cpl[ch]) {
                        double coord_f = 0.0;
                        if (amp[CPL_CH] > 0)
                            coord_f = sqrt(amp[ch] / amp[CPL_CH]) / 8.0;
                        /* TODO: 24-bit coupling coordinates */
                        /*coord[ch] = coord_f * 8388608.0;
                        coord[ch] = CLIP(coord[ch], 0, 8388607);
                        exp[ch] = (coord[ch] == 0) ? 24 : 23 - log2i(coord[ch]);
                        max_exp = MAX(max_exp, exp[ch]);
                        need_master |= (exp[ch] > 15 && exp[ch] != 24);*/
                        coord[ch] = coord_f * 32768.0;
                        coord[ch] = CLIP(coord[ch], 0, 32767);
                        exp[ch] = (coord[ch] == 0) ? 15 : 14 - log2i(coord[ch]);
                    }
                }

                /* TODO: determine master coupling coordinate */
                frame->blocks[blk].master_cpl_coord[ch] = 0;

                for (ch = 1; ch <= ctx->n_channels; ch++) {
                    if (frame->channel_in_cpl[ch]) {
                        int mant;
                        mant = (coord[ch] << 5) >> (15-exp[ch]);
                        frame->blocks[blk].cpl_coord_exp[ch][bnd] = exp[ch];
                        if (exp[ch] < 15)
                            frame->blocks[blk].cpl_coord_mant[ch][bnd] = mant - 16;
                        else
                            frame->blocks[blk].cpl_coord_mant[ch][bnd] = 0;
                    }
                }
            }
            j += frame->cpl_band_size[bnd];
        }
    }
}

static void calc_rematrixing(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;
    A52Block *block;
    double lt, rt, ctmp1, ctmp2;
    int blk, bnd, i;

    // initialize flags to zero
    for (bnd = 0; bnd < 4; bnd++)
        frame->rematflg[bnd] = 0;

    // if rematrixing is disabled, return
    if (!ctx->params.use_rematrixing)
        return;

    // calculate rematrixing flags and apply rematrixing to coefficients
    for (bnd = 0; bnd < frame->num_rematrixing_bands; bnd++) {
        // calculate sums for this band for all blocks
        double sum[4] = {0,};
        int max;
        for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
            block = &frame->blocks[blk];
            for (i = a52_rematrix_band_tab[bnd]; i < a52_rematrix_band_tab[bnd+1]; i++) {
                if (i == frame->end_freq[1])
                    break;
                lt = block->mdct_coef[1][i];
                rt = block->mdct_coef[2][i];
                sum[0] += lt * lt;
                sum[1] += rt * rt;
                sum[2] += (lt + rt) * (lt + rt);
                sum[3] += (lt - rt) * (lt - rt);
            }
        }
        // compare sums to determine if rematrixing is used for this band
        max = 0;
        for (i = 1; i < 4; i++) {
            if (sum[i] > sum[max])
                max = i;
        }
        if (max > 1) {
            frame->rematflg[bnd] = 1;
            // apply rematrixing in this band for all blocks
            for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
                block = &frame->blocks[blk];
                for (i = a52_rematrix_band_tab[bnd]; i < a52_rematrix_band_tab[bnd+1]; i++) {
                    if (i == frame->end_freq[1])
                        break;
                    ctmp1 = block->mdct_coef[1][i] * 0.5;
                    ctmp2 = block->mdct_coef[2][i] * 0.5;
                    block->mdct_coef[1][i] = ctmp1 + ctmp2;
                    block->mdct_coef[2][i] = ctmp1 - ctmp2;
                }
            }
        }
    }
}

/** Adjust for fractional frame sizes in CBR mode */
static void adjust_frame_size(A52Context *ctx)
{
    A52Frame *f = &ctx->frame;
    uint32_t kbps = f->bit_rate * 1000;
    uint32_t srate = ctx->sample_rate;
    int add;

    while (ctx->bit_cnt >= kbps && ctx->sample_cnt >= srate) {
        ctx->bit_cnt -= kbps;
        ctx->sample_cnt -= srate;
    }
    add = !!(ctx->bit_cnt * srate < ctx->sample_cnt * kbps);
    f->frame_size = f->frame_size_min + add;
}

static void compute_dither_strategy(A52Context *ctx)
{
    A52Block *block0;
    A52Block *block1;
    int blk, ch;

    block0 = NULL;
    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block1 = &ctx->frame.blocks[blk];
        for (ch = 1; ch <= ctx->n_channels; ch++) {
            if (block1->blksw[ch] || ((blk>0) && block0->blksw[ch]))
                block1->dithflag[ch] = 0;
            else
                block1->dithflag[ch] = 1;
        }
        block0 = block1;
    }
}

static void calculate_dynrng(A52Context *ctx)
{
    A52Block *block;
    int blk;

    if (ctx->params.dynrng_profile == DYNRNG_PROFILE_NONE)
        return;

    for (blk = 0; blk < A52_NUM_BLOCKS; blk++) {
        block = &ctx->frame.blocks[blk];
        block->dynrng = a52_dynrng_calc_block(block->input_samples,
                                              ctx->n_all_channels,
                                              -ctx->meta.dialnorm,
                                              ctx->params.dynrng_profile);
    }
}

static int encode_frame(A52Context *ctx)
{
    A52Frame *frame = &ctx->frame;

    if (frame_init(ctx)) {
        fprintf(stderr, "Encoding has not properly initialized\n");
        return -1;
    }

    copy_samples(ctx);

    calculate_dynrng(ctx);

    generate_coefs(ctx);

    store_samples(ctx);

    compute_dither_strategy(ctx);

    if (ctx->acmod == A52_ACMOD_STEREO)
        calc_rematrixing(ctx);

    // variable bandwidth
    if (ctx->params.bwcode == -2) {
        // process exponents at full bandwidth
        a52_exponent_process(ctx);
        // run bit allocation at q=240 to calculate bandwidth
        a52_vbw_bit_allocation(ctx);
    }

    a52_exponent_process(ctx);

    if (ctx->params.encoding_mode == ALSOPHILA_ENC_MODE_CBR)
        adjust_frame_size(ctx);

    if (a52_compute_bit_allocation(ctx)) {
        fprintf(stderr, "Error in bit allocation\n");
        ctx->frame.frame_size = 0;
        return -1;
    }

    quantize_mantissas(ctx);

    // increment counters
    ctx->bit_cnt += frame->frame_size * 16;
    ctx->sample_cnt += A52_SAMPLES_PER_FRAME;

    // write output
    output_frame_header(ctx);
    a52_output_audio_blocks(ctx, 1);
    if (output_frame_end(ctx))
        return -1;

    // check for bitwriter EOF
    if (ctx->bw.eof) {
        fprintf(stderr, "buffer size too small for frame\n");
        return -1;
    }

    return 0;
}

static int convert_samples_from_src(A52Context *ctx, const void *vsrc,
                                    int count)
{
    ctx->fmt_convert_from_src(ctx->frame.input_samples, vsrc, ctx->n_all_channels, count);
    if (count < A52_SAMPLES_PER_FRAME) {
        int ch;
        for (ch = 0; ch < ctx->n_all_channels; ch++)
            memset(&ctx->frame.input_samples[ch][count], 0,
                   (A52_SAMPLES_PER_FRAME - count) * sizeof(double));
    }
    return 0;
}

int alsophila_encode_frame(AlsophilaContext *s, AlsophilaFrame *frame,
                           const void *samples, int count)
{
    A52Context *ctx;

    assert(s != NULL && frame != NULL);
    assert(!(count > 0 && samples == NULL));

    if (count > A52_SAMPLES_PER_FRAME || count < 0) {
        fprintf(stderr, "invalid sample count: %d\n", count);
        return -1;
    }
    ctx = s->private_context;
    if (count && ctx->last_samples_count != -1 && ctx->last_samples_count < A52_SAMPLES_PER_FRAME) {
        fprintf(stderr, "count must be 0 after having once been <A52_SAMPLES_PER_FRAME when passed to alsophila_encode_frame\n");
        return -1;
    }

    // append extra silent frame if final frame is > 1280 samples, to flush 256 samples in mdct
    if (ctx->last_samples_count <= (A52_SAMPLES_PER_FRAME - 256) && ctx->last_samples_count != -1)
        return 0;

    convert_samples_from_src(ctx, samples, count);

    if (!frame->buffer || frame->buffer_size <= A52_MIN_CODED_FRAME_SIZE) {
        fprintf(stderr, "output buffer is NULL or too small\n");
        return -1;
    }
    bitwriter_init(&ctx->bw, frame->buffer, frame->buffer_size);

    if (encode_frame(ctx))
        return -1;
    ctx->last_samples_count = count;

    frame->frame_size = ctx->frame.frame_size << 1;
    frame->quality    = ctx->frame.quality;
    frame->bit_rate   = ctx->frame.bit_rate;
    frame->bwcode     = ctx->frame.bwcode;

    return 1;
}

void alsophila_encode_close(AlsophilaContext *s)
{
    if (s != NULL && s->private_context != NULL) {
        A52Context *ctx = s->private_context;
        int ch;

        // free input audio buffer
        free(ctx->frame.input_audio);

        // mdct_close deinits both mdcts
        a52_mdct_close(ctx);

        // close input filters
        a52_filter_close(&ctx->lfe_filter);
        for (ch = 0; ch < A52_MAX_CHANNELS; ch++) {
            a52_filter_close(&ctx->bs_filter[ch]);
            a52_filter_close(&ctx->dc_filter[ch]);
            a52_filter_close(&ctx->bw_filter[ch]);
        }

        free(ctx);
        s->private_context = NULL;
    }
}
